#! /usr/bin/env python
# -*- coding: utf8 -*-
#
#  gitlab irc sender
#  Copyright (C) 2016-2017  Andrei Karas (4144)
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import json
import re

from code.codeshipirchandler import CodeshipIrcHandler
from code.core import Core
from code.githubirchandler import GithubIrcHandler
from code.irchandler import IrcHandler
from code.logger import Logger

eolSplit = re.compile("\n")

class ActionHandler():
    def printInfo(self, text):
        if Core.config.show_info == True:
            Logger.addLog(text)

    def saveJson(self, data):
        Core.log.info(json.dumps(data, indent = 2))

    def findProject(self, path):
        if path in Core.config.projects:
            self.project = Core.config.projects[path]
            return self.project
        self.project = None
        Logger.addError("Wrong url")
        return None

    def checkProject(self, data):
        if "project" in data:
            # gitlab project name
            prj = data["project"]["name"]
        elif "repository" in data:
            # gitlab or github project name
            prj = data["repository"]["name"]
        elif "build" in data:
            # codeship project name
            prj = data["build"]["project_name"]
        else:
            prj = "Unknown"
        if prj != self.project.name:
            Logger.addError("Wrong project. Asked for project {0} but we found {1}".format(prj, self.project.name))
            return False
        return True


    def isEnabled(self, object_kind):
        if object_kind in self.project.actions:
            return True
        return False

    def validate(self, path, data):
        self.project = self.findProject(path)
        if self.project is None:
            return False
        if self.checkProject(data) == False:
            return False
        return True

    def shortenBranch(self, branch):
        if len(branch) > 11 and branch[:11] == "refs/heads/":
            branch = branch[11:]
        return branch

    def isAllowBranch(self, branch, allow_list, deny_list):
        if branch in allow_list:
            return True
        if branch in deny_list:
            return False
        if "*" in allow_list:
            return True
        if "*" in deny_list:
            return False

    def processAction(self, path, data, headers):
        Logger.addLog("Request: " + path)
        Logger.addLog("Accesing project: " + self.project.name)
        service = self.project.options.service
        if service == "gitlab":
            self.processGitlabAction(path, data)
        elif service == "github":
            self.processGithubAction(path, data, headers["X-GitHub-Event"])
        elif service == "codeship":
            self.processCodeshipAction(path, data)
        else:
            Logger.addLog("Unknown service: " + service)


    def processCodeshipAction(self, path, data):
        if "build" not in data:
            Logger.addLog("Unknown json for CodeShip")
            return
        build = data["build"]
        branch = self.shortenBranch(build["branch"])
        channels = self.project.channels
        allow_branches = self.project.allow_branches
        deny_brancges = self.project.deny_branches
        if self.isAllowBranch(branch, allow_branches, deny_brancges) == False:
            return
        self.printInfo(build["build_url"])
        commit_id = build["commit_id"]
        commit_url = build["commit_url"]
        url = build["build_url"]
        build_id = build["build_id"]
        build_status = build["status"]
        ircHandler = CodeshipIrcHandler()
        if build_status == "error":
            for channel in channels:
                options = self.project.channels[channel]
                ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit_id, commit_url, url, build_id)
                if len(options.build_fail_custom_message) > 0:
                    ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
            return
        elif build_status == "success":
            found = False
            for channel in channels:
                ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit_id, commit_url, url, build_id)
                found = True
            if found == True:
                return
        elif build_status == "waiting":
            pass
        else:
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)
            Logger.addLog("Skipping unsupported build status {0}.".format(build_status))
            return
        Logger.addLog("Unused build status {0}.".format(build_status))



    def processGitlabAction(self, path, data):
        object_kind = data["object_kind"]
        if self.isEnabled(object_kind) == False:
            Logger.addLog("Ignoring action: " + object_kind)
            return
        Logger.addLog("Accepting action: " + object_kind)
        channels = self.project.actions[object_kind]
        allow_branches = self.project.allow_branches
        deny_brancges = self.project.deny_branches
        if len(channels) == 0:
            Logger.addLog("no channels in action")
            return

        if object_kind == "push":
            branch = self.shortenBranch(data["ref"])
            if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
                self.push(data, channels, branch)
        elif object_kind == "tag_push":
            self.tag_push(data, channels)
        elif object_kind == "note":
            self.note(data, channels)
        elif object_kind == "build":
            branch = self.shortenBranch(data["ref"])
            if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
                self.build(data, channels, branch)
        elif object_kind == "pipeline":
            branch = self.shortenBranch(data["object_attributes"]["ref"])
            if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
                self.pipeline(data, channels, branch)
        elif object_kind == "merge_request":
            self.merge_request(data, channels)
        elif object_kind == "issue":
            self.issue(data, channels)
        else:
            Logger.addLog("Ignoring unsupported action: " + object_kind)
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def processGithubAction(self, path, data, object_kind):
        if self.isEnabled(object_kind) == False:
            Logger.addLog("Ignoring github action: " + object_kind)
            return
        Logger.addLog("Accepting github action: " + object_kind)
        channels = self.project.actions[object_kind]
        allow_branches = self.project.allow_branches
        deny_brancges = self.project.deny_branches
        if len(channels) == 0:
            Logger.addLog("no channels in action")
            return
        if object_kind == "status":
            branches = data["branches"]
            for br in branches:
                branch = self.shortenBranch(br["name"])
                if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
                    self.github_status(data, channels, branch)
        elif object_kind == "issues":
            self.github_issues(data, channels)
        elif object_kind == "issue_comment":
            self.github_issue_comment(data, channels)
        elif object_kind == "check_suite":
            branch = data["check_suite"]["head_branch"]
            if self.isAllowBranch(branch, allow_branches, deny_brancges) == True:
                self.github_check_suite(data, channels, branch)
        else:
            Logger.addLog("Ignoring unsupported github action: " + object_kind)
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def splitMessageToParts(self, data):
        parts = eolSplit.split(data)
        lines = []
        for part in parts:
            if len(part) == 0:
                continue
            lines.append(part)
        return lines

    def push(self, data, channels, branch):
        self.printInfo(data["project"]["homepage"])
        commitsCount = data["total_commits_count"]
        commits = data["commits"]
        url = data["project"]["homepage"]
        ircHandler = IrcHandler()
        if len(commits) > 0:
            for channel in channels:
                options = self.project.channels[channel]
                ircHandler.printPushHeader(channel, self.project.name, branch, url, data, commits, commitsCount)
                if options.show_commits == False:
                    continue
                # if first push and fix enabled
                if options.push_first_push_fix == True and data["before"] == "0000000000000000000000000000000000000000":
                    commits_list = reversed(commits)
                else:
                    commits_list = commits

                for commit in commits_list:
                    messages = self.splitMessageToParts(commit["message"])
                    if len(messages) == 0:
                        continue
                    ircHandler.printCommitAuthor(channel, self.project.name, data, commit, messages[0])
                    if options.show_commit_messages == True:
                        del messages[0]
                        if options.max_message_lines_per_commit != 0:
                            while len(messages) > options.max_message_lines_per_commit:
                                del messages[len(messages) - 1]
                        ircHandler.printCommitMessage(channel, messages)
        else:
            for channel in channels:
                ircHandler.printCreateBranch(channel, self.project.name, data, branch, url)

    def tag_push(self, data, channels):
        self.printInfo(data["project"]["homepage"])
        commits = data["commits"]
        tag = data["ref"]
        if len(tag) > 10 and tag[:10] == "refs/tags/":
            tag = tag[10:]
        url = data["project"]["homepage"]
        ircHandler = IrcHandler()
        for channel in channels:
            ircHandler.printPushTag(channel, self.project.name, data, commits, tag, url)

    def note(self, data, channels):
        self.printInfo(data["project"]["homepage"])
        attrs = data["object_attributes"]
        url = attrs["url"]
        if "commit" in data:
            commit = data["commit"]
            url = data["project"]["homepage"]
            iid = attrs["id"]
            ircHandler = IrcHandler()
            for channel in channels:
                ircHandler.printAddCommitNote(channel, self.project.name, data, commit, url, iid)
        elif "merge_request" in data:
            ircHandler = IrcHandler()
            merge_request = data["merge_request"]
            iid = merge_request["iid"]
            branch = merge_request["target_branch"]
            source = merge_request["source"]
            title = merge_request["title"]
            source_branch = merge_request["source_branch"]
            source_namespace = source["namespace"]
            for channel in channels:
                ircHandler.printAddMRNote(channel, self.project.name, branch, source_namespace, source_branch, data, url, iid)
        elif "issue" in data:
            ircHandler = IrcHandler()
            issue = data["issue"]
            iid = issue["iid"]
            title = issue["title"]
            for channel in channels:
                ircHandler.printAddIssueNote(channel, self.project.name, data, url, iid, title)
        elif "snippet" in data:
            ircHandler = IrcHandler()
            snippet = data["snippet"]
            iid = snippet["id"]
            title = snippet["title"]
            for channel in channels:
                ircHandler.printAddSnippetNote(channel, self.project.name, data, url, iid, title)
        else:
            Logger.addLog("Skipping unsupported note type");
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)

    def build(self, data, channels, branch):
        self.printInfo(data["repository"]["homepage"])
        commit = data["commit"]
        url = data["repository"]["homepage"]
        build_id = data["build_id"]
        build_status = data["build_status"]
        ircHandler = IrcHandler()
        build_stage = data["build_stage"]
        build_name = data["build_name"]
        build_allow_failure = data["build_allow_failure"]
        if build_status == "failed":
            options = self.project.options
            build_retry = options.build_retry
            if options.gitlab_api_token != "" and build_retry != "never" and build_retry != "":
                Core.apiHandler.retry_build(data["project_id"], build_id, data["sha"], options)
            for channel in channels:
                options = self.project.channels[channel]
                if len(options.last_build_fail_stage) > 0 and data["build_stage"] == options.last_build_fail_stage:
                    ircHandler.printStageFailed(channel, self.project.name, branch, data, commit, url, build_id)
                if options.show_build_failures_for_allowed_to_fail == True and data["build_allow_failure"] == True:
                    ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, build_id, build_stage, build_name)
                    if len(options.build_fail_custom_message) > 0:
                        ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
                    continue  # for prevent double messages
                if options.show_build_failures == False:
                    continue
                if options.build_accept_allow_failure == False or build_allow_failure == False:
                    ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, build_id, build_stage, build_name)
                    if len(options.build_fail_custom_message) > 0:
                        ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
            return
        elif build_status == "success":
            found = False
            for channel in channels:
                options = self.project.channels[channel]
                if len(options.last_build_fail_stage) > 0 and data["build_stage"] == options.last_build_fail_stage:
                    ircHandler.printStageFailed(channel, self.project.name, branch, data, commit, url, build_id)
                    found = True
                if len(options.last_build_stage) > 0 and data["build_stage"] == options.last_build_stage:
                    ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, build_id)
                    found = True
                if options.show_build_success_for_allowed_to_fail == True and data["build_allow_failure"] == True:
                    ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, build_id)
                    found = True
            if found == True:
                return
        elif build_status == "pending":
            pass
        elif build_status == "running":
            pass
        elif build_status == "canceled":
            pass
        else:
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)
            Logger.addLog("Skipping unsupported build status {0}.".format(build_status))
            return
        Logger.addLog("Unused build status {0}.".format(build_status))

    def pipeline(self, data, channels, branch):
        self.printInfo(data["project"]["web_url"])
        commit = data["commit"]
        url = data["project"]["web_url"]
        pipeline_status = data["object_attributes"]["status"]
        ircHandler = IrcHandler()
        pipeline_id = data["object_attributes"]["id"]
        if pipeline_status == "failed":
            if self.isPipelineComplete(data) == True:
                for channel in channels:
                    options = self.project.channels[channel]
                    if options.show_pipeline_failures == False:
                        continue
                    ircHandler.printPipelineFailed(channel, self.project.name, branch, data, commit, url, pipeline_id)
                    if len(options.pipeline_fail_custom_message) > 0:
                        ircHandler.printCustomMessage(channel, options.pipeline_fail_custom_message)
            return
        elif pipeline_status == "success":
            if self.isPipelineComplete(data) == True:
                for channel in channels:
                    options = self.project.channels[channel]
                    if options.show_pipeline_success == False:
                        continue
                    ircHandler.printPipelineSuccess(channel, self.project.name, branch, data, commit, url, pipeline_id)
                return
        elif pipeline_status == "pending":
            pass
        elif pipeline_status == "running":
            pass
        elif pipeline_status == "canceled":
            pass
        else:
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)
            Logger.addLog("Skipping unsupported pipeline status {0}.".format(pipeline_status))
            return
        Logger.addLog("Unused pipeline status {0}.".format(pipeline_status))

    def merge_request(self, data, channels):
        self.printInfo(data["project"]["homepage"])
        ircHandler = IrcHandler()
        attrs = data["object_attributes"]
        url = attrs["url"]
        action = attrs["action"]
        iid = attrs["iid"]
        branch = attrs["target_branch"]
        source = attrs["source"]
        title = attrs["title"]
        source_branch = attrs["source_branch"]
        source_namespace = source["namespace"]
        if action == "open":
            for channel in channels:
                ircHandler.printMROpened(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
        elif action == "close":
            for channel in channels:
                ircHandler.printMRClosed(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
        elif action == "reopen":
            for channel in channels:
                ircHandler.printMRReopened(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
        elif action == "update":
            for channel in channels:
                ircHandler.printMRUpdated(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
        elif action == "merge":
            for channel in channels:
                ircHandler.printMRMerged(channel, self.project.name, branch, source_branch, source_namespace, data, url, iid, title)
        else:
            Logger.addLog("Skipping unsupported action {0}.".format(action))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)

    def issue(self, data, channels):
        self.printInfo(data["project"]["homepage"])
        ircHandler = IrcHandler()
        attrs = data["object_attributes"]
        url = attrs["url"]
        action = attrs["action"]
        iid = attrs["iid"]
        title = attrs["title"]
        if action == "open":
            for channel in channels:
                ircHandler.printIssueOpened(channel, self.project.name, data, url, iid, title)
        elif action == "close":
            for channel in channels:
                ircHandler.printIssueClosed(channel, self.project.name, data, url, iid, title)
        elif action == "reopen":
            for channel in channels:
                ircHandler.printIssueReopened(channel, self.project.name, data, url, iid, title)
        elif action == "update":
            for channel in channels:
                ircHandler.printIssueUpdated(channel, self.project.name, data, url, iid, title)
        else:
            Logger.addLog("Skipping unsupported action {0}.".format(action))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def github_status(self, data, channels, branch):
        self.printInfo(data["repository"]["html_url"])
        ircHandler = GithubIrcHandler()
        action = data["state"]
        description = data["description"]
        commit = data["commit"]["sha"]
        url = data["target_url"]
        if action == "pending":
            pass
        elif action == "success":
            for channel in channels:
                ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, description)
        elif action == "failure" or action == "error":
            for channel in channels:
                options = self.project.channels[channel]
                if options.show_build_failures == False:
                    continue
                ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, description)
                if len(options.build_fail_custom_message) > 0:
                    ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
        else:
            Logger.addLog("Skipping unsupported github status action {0}.".format(action))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def github_check_suite(self, data, channels, branch):
        self.printInfo(data["repository"]["html_url"])
        ircHandler = GithubIrcHandler()
        action = data["action"]
        conclusion = data["check_suite"]["conclusion"]
        status = data["check_suite"]["status"]
        commit = data["check_suite"]["head_sha"]
        # tested only on azure pipelines, and here actual url missing
        url = data["repository"]["html_url"] + "/commits/" + commit
        # tested only on azure pipelines, and here actual description missing
        description = data["check_suite"]["app"]["name"]
        if action == "completed":
            if conclusion == "failure" or conclusion == "cancelled" or conclusion == "timed_out":
                description = description + " build " + conclusion.replace("_", " ")
                for channel in channels:
                    options = self.project.channels[channel]
                    if options.show_build_failures == False:
                        continue
                    ircHandler.printBuildFailed(channel, self.project.name, branch, data, commit, url, description)
                    if len(options.build_fail_custom_message) > 0:
                        ircHandler.printCustomMessage(channel, options.build_fail_custom_message)
            elif conclusion == "success" or conclusion == "action_required":
                description = description + " build " + conclusion.replace("_", " ")
                for channel in channels:
                    ircHandler.printBuildSuccess(channel, self.project.name, branch, data, commit, url, description)
            else:
                Logger.addLog("Skipping unsupported github check_suite action {0} / {1} / {2}.".format(action, status, conclusion))
                if Core.config.global_options.http_save_json == "unsupported":
                    self.saveJson(data)
        else:
            Logger.addLog("Skipping unsupported github check_suite action {0} / {1} / {2}.".format(action, status, conclusion))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def github_issues(self, data, channels):
        self.printInfo(data["repository"]["html_url"])
        ircHandler = GithubIrcHandler()
        action = data["action"]
        url = data["issue"]["html_url"]
        description = data["issue"]["title"]
        iid = data["issue"]["number"]
        if action == "opened":
            for channel in channels:
                ircHandler.printIssuesOpened(channel, self.project.name, data, url, iid, description)
        elif action == "edited":
            for channel in channels:
                ircHandler.printIssuesEdited(channel, self.project.name, data, url, iid, description)
        elif action == "closed":
            for channel in channels:
                ircHandler.printIssuesClosed(channel, self.project.name, data, url, iid, description)
        elif action == "reopened":
            for channel in channels:
                ircHandler.printIssuesReopened(channel, self.project.name, data, url, iid, description)
        else:
            Logger.addLog("Skipping unsupported github issues action {0}.".format(action))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def github_issue_comment(self, data, channels):
        self.printInfo(data["repository"]["html_url"])
        ircHandler = GithubIrcHandler()
        action = data["action"]
        url = data["issue"]["html_url"]
        description = data["issue"]["title"]
        iid = data["issue"]["number"]
        if action == "created":
            for channel in channels:
                ircHandler.printIssueCommentCreated(channel, self.project.name, data, url, iid, description)
        elif action == "edited":
            for channel in channels:
                ircHandler.printIssueCommentEdited(channel, self.project.name, data, url, iid, description)
        elif action == "deleted":
            for channel in channels:
                ircHandler.printIssueCommentDeleted(channel, self.project.name, data, url, iid, description)
        else:
            Logger.addLog("Skipping unsupported github issue_comment action {0}.".format(action))
            if Core.config.global_options.http_save_json == "unsupported":
                self.saveJson(data)


    def isPipelineComplete(self, data):
        for build in data["builds"]:
            status = build["status"]
            if status != "success" and status != "failed" and status != "canceled" and status != "skipped":
                return False
        return True
